#include "wl_list.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "wl_malloc.h"

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

wl_int32 wl_list_init(wl_list* list) {
    if (list == wl_null) {
        return -1;
    }
    memset(list, 0, sizeof(wl_list));
    return 0;
}

void wl_list_free(wl_list* li) {
    wl_int32 pos = 0;
    char*    chain;

    if (li == wl_null) {
        return;
    }
    while (!wl_list_eol(li, pos)) {
        chain = (char*)wl_list_get(li, pos);
        wl_list_remove(li, pos);
        wl_free(chain);
    }
}

void wl_list_special_free(wl_list* li, void (*free_func)(void*)) {
    void* element;

    if (li == wl_null) {
        return;
    }
    while (!wl_list_eol(li, 0)) {
        element = (void*)wl_list_get(li, 0);
        wl_list_remove(li, 0);
        if (free_func != wl_null)
            free_func(element);
    }
}

wl_int32 wl_list_clone(const wl_list* src, wl_list* dst, wl_int32 (*clone_func)(void*, void**)) {
    void*            data;
    void*            data2;
    wl_int32         i;
    wl_list_iterator iterator;

    for (data = wl_list_get_first((wl_list*)src, &iterator); !wl_list_iterator_is_end(&iterator);
         data = wl_list_get_next(&iterator)) {
        i = clone_func(data, &data2);
        if (i != 0) {
            return i;
        }
        wl_list_add(dst, data2, -1);
    }
    return 0;
}

wl_int32 wl_list_size(const wl_list* li) {
    if (li == wl_null) {
        return -1;
    }

    return li->nb_elt;
}

wl_int32 wl_list_eol(const wl_list* li, wl_int32 i) {
    if (li == wl_null) {
        return -1;
    }
    if (i < li->nb_elt) {
        return 0; /* not end of list */
    }
    return 1; /* end of list */
}

/* index starts from 0; */
wl_int32 wl_list_add(wl_list* li, void* el, wl_int32 pos) {
    wl_list_node* ntmp;
    wl_int32      i = 0;

    if (li == wl_null) {
        return -1;
    }

    if (li->nb_elt == 0) {
        li->node = (wl_list_node*)wl_malloc(sizeof(wl_list_node));
        if (li->node == wl_null)
            return -1;
        li->node->element = el;
        li->node->next    = wl_null;
        li->nb_elt++;
        return li->nb_elt;
    }

    if (pos == -1 || pos >= li->nb_elt) { /* insert at the end  */
        pos = li->nb_elt;
    }

    ntmp = li->node; /* exist because nb_elt>0  */

    if (pos == 0) { /* pos = 0 insert before first elt  */
        li->node = (wl_list_node*)wl_malloc(sizeof(wl_list_node));
        if (li->node == wl_null) {
            /* leave the list unchanged */
            li->node = ntmp;
            return -1;
        }
        li->node->element = el;
        li->node->next    = ntmp;
        li->nb_elt++;
        return li->nb_elt;
    }

    while (pos > i + 1) {
        i++;
        /* when pos>i next node exist  */
        ntmp = ntmp->next;
    }

    /* if pos==nb_elt next node does not exist  */
    if (pos == li->nb_elt) {
        ntmp->next = (wl_list_node*)wl_malloc(sizeof(wl_list_node));
        if (ntmp->next == wl_null) {
            return -1; /* leave the list unchanged */
        }
        ntmp          = ntmp->next;
        ntmp->element = el;
        ntmp->next    = wl_null;
        li->nb_elt++;
        return li->nb_elt;
    }

    /* here pos==i so next node is where we want to insert new node */
    {
        wl_list_node* nextnode = ntmp->next;

        ntmp->next = (wl_list_node*)wl_malloc(sizeof(wl_list_node));
        if (ntmp->next == wl_null) {
            /* leave the list unchanged */
            ntmp->next = nextnode;
            return -1;
        }
        ntmp          = ntmp->next;
        ntmp->element = el;
        ntmp->next    = nextnode;
        li->nb_elt++;
    }
    return li->nb_elt;
}

/* index starts from 0 */
void* wl_list_get(const wl_list* li, wl_int32 pos) {
    wl_list_node* ntmp;
    wl_int32      i = 0;

    if (li == wl_null) {
        return wl_null;
    }

    if (pos < 0 || pos >= li->nb_elt) {
        /* element does not exist */
        return wl_null;
    }

    ntmp = li->node; /* exist because nb_elt>0 */

    while (pos > i) {
        i++;
        ntmp = ntmp->next;
    }
    return ntmp->element;
}

void* wl_list_get_first(wl_list* li, wl_list_iterator* iterator) {
    if (0 >= li->nb_elt) {
        iterator->actual = 0;
        return wl_null;
    }

    iterator->actual = li->node;
    iterator->prev   = &li->node;
    iterator->li     = li;
    iterator->pos    = 0;

    return li->node->element;
}

void* wl_list_get_next(wl_list_iterator* iterator) {
    iterator->prev   = &(iterator->actual->next);
    iterator->actual = iterator->actual->next;
    ++(iterator->pos);

    if (!wl_list_iterator_is_end(iterator)) {
        return iterator->actual->element;
    }

    iterator->actual = 0;
    return wl_null;
}

wl_bool wl_list_iterator_is_end(wl_list_iterator* iterator) {
    if (iterator) {
        return (wl_null == iterator->actual || iterator->pos >= iterator->li->nb_elt);
    }
    return wl_true;
}

void* wl_list_iterator_remove(wl_list_iterator* iterator) {
    if (!wl_list_iterator_is_end(iterator)) {
        --(iterator->li->nb_elt);

        *(iterator->prev) = iterator->actual->next;

        wl_free(iterator->actual);
        iterator->actual = *(iterator->prev);
    }

    if (!wl_list_iterator_is_end(iterator)) {
        return iterator->actual->element;
    }

    return wl_null;
}

/* return -1 if failed */
wl_int32 wl_list_remove(wl_list* li, wl_int32 pos) {
    wl_list_node* ntmp;
    wl_int32      i = 0;

    if (li == wl_null) {
        return -1;
    }

    if (pos < 0 || pos >= li->nb_elt) {
        /* element does not exist */
        return -1;
    }

    ntmp = li->node; /* exist because nb_elt>0 */

    if ((pos == 0)) { /* special case  */
        li->node = ntmp->next;
        li->nb_elt--;
        wl_free(ntmp);
        return li->nb_elt;
    }

    while (pos > i + 1) {
        i++;
        ntmp = ntmp->next;
    }

    /* insert new node */
    {
        wl_list_node* remnode;

        remnode    = ntmp->next;
        ntmp->next = (ntmp->next)->next;
        wl_free(remnode);
        li->nb_elt--;
    }
    return li->nb_elt;
}

#ifdef __cplusplus
}
#endif /* __cplusplus */
